From a3b47ad4f05d6fa3970cc2e295f9bc9ee9c13efa Mon Sep 17 00:00:00 2001 From: robertl Date: Wed, 24 Jun 2009 03:24:23 +0000 Subject: [PATCH] Add delbin support from Paul Cornett. --- Makefile.in | 2 +- delbin.c | 2748 +++++++++++++++++++++++++++++++++++++++++++++++++++ vecs.c | 7 + 3 files changed, 2756 insertions(+), 1 deletion(-) create mode 100644 delbin.c diff --git a/Makefile.in b/Makefile.in index 855369caa..f905aacba 100644 --- a/Makefile.in +++ b/Makefile.in @@ -62,7 +62,7 @@ ALL_FMTS=$(MINIMAL_FMTS) gtm.o gpsutil.o pcx.o cetus.o copilot.o \ navilink.o mtk_logger.o ik3d.o osm.o destinator.o exif.o vidaone.o \ igo8.o gopal.o humminbird.o mapasia.o gnav_trl.o navitel.o ggv_ovl.o \ jtr.o sbp.o sbn.o mmo.o skyforce.o itracku.o v900.o \ - pocketfms_bc.o pocketfms_fp.o naviguide.o + pocketfms_bc.o pocketfms_fp.o naviguide.o delbin.o FMTS=@FMTS@ diff --git a/delbin.c b/delbin.c new file mode 100644 index 000000000..0fdc6e45a --- /dev/null +++ b/delbin.c @@ -0,0 +1,2748 @@ +/* + DeLorme PN-20/40 USB "DeLBin" protocol + + Copyright (C) 2009 Paul Cornett, pc-gpsb at bullseye.com + Copyright (C) 2005 Robert Lipe, robertlipe@usa.net + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA + + */ + +#include "defs.h" +#include + +#define MYNAME "delbin" + +/* +Device documentation: +"DeLorme Binary GPS Format", delbin_user_interface_format_176.pdf +obtained here: http://forum.delorme.com/viewtopic.php?t=13846 + +Notes: +Initial development was done with a PN-40, firmware 2.4.123299. The test +device was upgraded to firmware 2.5.165506 during development. + +The "data size" in the message header includes the 4 trailer bytes, so it +is really the size of the whole message minus the header. + +The time before an unacknowledged message will be retransmitted by the +device is on the order of 2 to 4 seconds. + +Retrieving all tracks at once (using code 0 in message 0xb031) does not +seem to work, it hangs after the first track, maybe waiting for some +undocumented response message. + +Character encoding is not documented, appears to be 8859-1. + +The undocumented messages 0xaa01, 0xb015, 0xb016 and the use of the +"reserved" byte in message 0xb012 were discovered by examining the data +transferred between the device and DeLorme Topo 8.0. They may have been +added in the PN-40 2.5 firmware. +*/ + +//----------------------------------------------------------------------------- +// interface to platform-specific device I/O +typedef struct { + void (*init)(const char* name); + void (*deinit)(void); + unsigned (*packet_read)(void*); + unsigned (*packet_write)(const void*, unsigned); +} delbin_os_ops_t; + +// really static, only extern so it can be forward declared +extern delbin_os_ops_t delbin_os_ops; + +static unsigned delbin_os_packet_size; +//----------------------------------------------------------------------------- + +// number of times to attempt a transfer before giving up +#define ATTEMPT_MAX 2 + +// debug output: low, medium, high +#define DBGLVL_L 1 +#define DBGLVL_M 2 +#define DBGLVL_H 3 + +#define UNKNOWN_ELEV -2000000 + +#define sizeofarray(x) (sizeof(x) / sizeof(x[0])) + +static char* opt_getposn; +static char* opt_logs; +static char* opt_long_notes; + +static arglist_t delbin_args[] = { + { "get_posn", &opt_getposn, "Return current position as a waypoint", + NULL, ARGTYPE_BOOL, ARG_NOMINMAX }, + { "logs", &opt_logs, "Include groundspeak logs when writing", + NULL, ARGTYPE_BOOL, ARG_NOMINMAX }, + { "long_notes", &opt_long_notes, "Use long waypoint notes regardless of PN version", + NULL, ARGTYPE_BOOL, ARG_NOMINMAX }, + ARG_TERMINATOR +}; + +// Whether device understands message 0xb016 +static int use_extended_notes; + +static const char* waypoint_symbol(unsigned index); +static unsigned waypoint_symbol_index(const char* name); +static int track_color(unsigned index); +static unsigned track_color_index(int bgr); + +static unsigned waypoint_i; +static unsigned waypoint_n; +static waypoint** wp_array; + +//----------------------------------------------------------------------------- +// Message ids and sizes. Only the needed ones are here. +// Note that "in" and "out" ids are named as in the device documentation, +// so "in" means to the device, "out" means from. +#define MSG_ACK 0xaa00 +#define MSG_BREAK 0xaa02 +#define MSG_BREAK_SIZE 33 +#define MSG_NAVIGATION 0xa010 +#define MSG_REQUEST_ROUTES 0xb051 +#define MSG_REQUEST_ROUTES_SIZE 65 +#define MSG_REQUEST_TRACKS 0xb031 +#define MSG_REQUEST_TRACKS_SIZE 33 +#define MSG_REQUEST_WAYPOINTS 0xb012 +#define MSG_REQUEST_WAYPOINTS_SIZE 15 +#define MSG_ROUTE_COUNT 0xb050 +#define MSG_ROUTE_HEADER_IN 0xb055 +#define MSG_ROUTE_HEADER_OUT 0xb052 +#define MSG_ROUTE_POINT_IN 0xb056 +#define MSG_ROUTE_POINT_OUT 0xb053 +#define MSG_ROUTE_SHAPE_IN 0xb057 +#define MSG_ROUTE_SHAPE_OUT 0xb054 +#define MSG_SATELLITE_INFO 0xa020 +#define MSG_TRACK_COUNT 0xb030 +#define MSG_TRACK_HEADER_IN 0xb035 +#define MSG_TRACK_HEADER_OUT 0xb032 +#define MSG_TRACK_POINT_IN 0xb036 +#define MSG_TRACK_POINT_OUT 0xb033 +#define MSG_TRANSFER_COMPLETE 0xaa04 +#define MSG_VERSION 0xa001 +#define MSG_WAYPOINT_COUNT 0xb010 +#define MSG_WAYPOINT_IN 0xb014 +#define MSG_WAYPOINT_OUT 0xb013 +// Undocumented: +// This one looks like MSG_ACK, except it also has a string in it that says +// something like "device is busy". The expected MSG_ACK usually immediately +// follows it, so the point of this one is unclear. +#define MSG_NACK 0xaa01 +// Long waypoint notes +#define MSG_WAYPOINT_NOTE_IN 0xb016 +#define MSG_WAYPOINT_NOTE_OUT 0xb015 + +//----------------------------------------------------------------------------- +// Message structures + +// Output Waypoint Message +// Message ID: 0xB013 +// Input Waypoint Message +// Message ID: 0xB014 +typedef struct { + gbuint8 total[4]; // U32 + gbuint8 index[4]; // U32 + gbuint8 year; + gbuint8 month; + gbuint8 day; + gbuint8 hour; + gbuint8 minute; + gbuint8 second; + gbuint8 latitude[4]; // S32 rad * 100000000 + gbuint8 longitude[4]; // S32 rad * 100000000 + gbuint8 elevation[4]; // F32 meters + gbuint8 color; + gbuint8 symbol; + gbuint8 name_size; + char name[1]; + // note_size[2] U16 + // note[note_size] +} msg_waypoint_t; + +// undocumented, seen with PN-40 2.5 firmware +// output waypoint note +// Message ID: 0xB015 +// input waypoint note +// Message ID: 0xB016 +typedef struct { + gbuint8 index[2]; + gbuint8 total[2]; + gbuint8 name_size; + char name[1]; + // note_size[2] + // note[note_size] +} msg_waypoint_note_t; + +// Output Track Point Message +// Message ID: 0xB033 +// Input Track Point Message +// Message ID: 0xB036 +typedef struct { + gbuint8 total[4]; // U32 + gbuint8 index[4]; // U32 + gbuint8 number; + struct { + gbuint8 year; + gbuint8 month; + gbuint8 day; + gbuint8 hour; + gbuint8 minute; + gbuint8 second; + gbuint8 latitude[4]; // S32 rad * 100000000 + gbuint8 longitude[4]; // S32 rad * 100000000 + gbuint8 elevation[4]; // F32 meters + gbuint8 speed[2]; // U16 km/h * 10 + gbuint8 heading[2]; // U16 deg * 100 + gbuint8 status; + } point[1]; +} msg_track_point_t; + +// Output Track Header (Name) Message +// Message ID: 0xB032 +typedef struct { + gbuint8 total_tracks[2]; // U16 + gbuint8 number[2]; // U16 + char name[32]; + gbuint8 total_points[4]; // U32 + gbuint8 year; + gbuint8 month; + gbuint8 day; + gbuint8 hour; + gbuint8 minute; + gbuint8 second; + gbuint8 color[2]; // U16 + gbuint8 distance[4]; // U32 m + gbuint8 duration[4]; // U32 sec + gbuint8 comment_size[2]; // U16 + char comment[1]; +} msg_track_header_t; + +// Input Upload Track Header Message +// Message ID: 0xB035 +typedef struct { + char name[32]; + gbuint8 total_points[4]; // U32 + gbuint8 year; + gbuint8 month; + gbuint8 day; + gbuint8 hour; + gbuint8 minute; + gbuint8 second; + gbuint8 color[2]; // U16 + gbuint8 comment_size[2]; // U16 + char comment[1]; +} msg_track_header_in_t; + +// Output Route Shape Message +// Message ID: 0xB054 +typedef struct { + gbuint8 total[4]; // U32 + gbuint8 index[4]; // U32 + gbuint8 number; + gbuint8 reserved; + struct { + gbuint8 latitude[4]; // S32 rad * 100000000 + gbuint8 longitude[4]; // S32 rad * 100000000 + } point[1]; +} msg_route_shape_t; + +// Output Route Point Message +// Message ID: 0xB053 +// Input Route Itin Point Message +// Message ID: 0xB056 +typedef struct { + gbuint8 total[4]; // U32 + gbuint8 index[4]; // U32 + char name[32]; + gbuint8 latitude[4]; // S32 rad * 100000000 + gbuint8 longitude[4]; // S32 rad * 100000000 + gbuint8 time_from_start[4]; // U32 sec + gbuint8 distance_from_start[4]; // F32 km + gbuint8 bearing_in[2]; // U16 deg * 100 + gbuint8 bearing_out[2]; // U16 deg * 100 + gbuint8 bearing_next[2]; // U16 deg * 100 + gbuint8 itinerary_type; + gbuint8 turn_type; + gbuint8 road_class[2]; // U16 + gbuint8 feature_code[4]; // U32 + gbuint8 exit_label_size; + char exit_label[1]; + // comment_size U8 + // comment[comment_size] + // shape_pt_count U32 +} msg_route_point_t; + +// Output Route Header (Name) Message +// Message ID: 0xB052 +typedef struct { + gbuint8 total[2]; // U16 + gbuint8 index[2]; // U16 + char name[64]; + gbuint8 type; + gbuint8 total_route_point[4]; // U32 + gbuint8 total_shape_point[4]; // U32 +} msg_route_header_t; + +// Input Upload Route Header Message +// Message ID: 0xB055 +typedef struct { + char name[64]; + gbuint8 type; + gbuint8 total_route_point[4]; // U32 + gbuint8 total_shape_point[4]; // U32 +} msg_route_header_in_t; + +// Output Navigation Message +// Message ID: 0xA010 +typedef struct { + gbuint8 gps_week[2]; // U16 + gbuint8 time_of_week[8]; // D64 sec + gbuint8 year[2]; // U16 + gbuint8 month; + gbuint8 day; + gbuint8 hour; + gbuint8 minute; + gbuint8 second; + gbuint8 satellites; + gbuint8 latitude[8]; // D64 deg + gbuint8 longitude[8]; // D64 deg + gbuint8 elevation[8]; // D64 meters + gbuint8 geoid_offset[2]; // S16 meters * 10 + gbuint8 speed[4]; // F32 km/h + gbuint8 heading[2]; // U16 deg * 100 + gbuint8 magnetic_variation[2]; // S16 deg * 100 + gbuint8 fix_status; +} msg_navigation_t; + +// Output Satellite Info Message +// Message ID: 0xA020 +typedef struct { + gbuint8 gps_week[2]; // U16 + gbuint8 time_of_week[8]; // D64 sec + gbuint8 hdop[2]; // U16 + gbuint8 vdop[2]; // U16 + gbuint8 pdop[2]; // U16 + gbuint8 number; + struct { + gbuint8 prn; + gbuint8 azimuth[2]; // S16 deg? * 100 + gbuint8 elevation[2]; // S16 deg? * 100 + gbuint8 Cn0[2]; // U16 snr * 100 + gbuint8 status; + } sat[1]; +} msg_satellite_t; + +// Output Version Message +// Message ID: 0xA001 +typedef struct { + gbuint8 firmware_version[4]; + char company[32]; + char product[32]; + char firmware[32]; + char gps_firmware[48]; + char serial[16]; + char extra[16]; +} msg_version_t; + +//----------------------------------------------------------------------------- + +static gbuint16 +checksum(const gbuint8* p, unsigned n) +{ + int x = 0; + unsigned i; + for (i = n / 2; i > 0; i--) { + x += *p++; + x += *p++ << 8; + } + if (n & 1) { + x += *p; + } + return (gbuint16)-x; +} + +//----------------------------------------------------------------------------- +// OS packet read/write wrappers + +static unsigned +packet_read(void* buf) +{ + unsigned n = delbin_os_ops.packet_read(buf); + if (n == 0) { + fatal(MYNAME ": read 0\n"); + } + if (global_opts.debug_level >= DBGLVL_H) { + unsigned j; + warning(MYNAME ": pcktrd "); + for (j = 0; j < n; j++) { + warning("%02x ", ((gbuint8*)buf)[j]); + } + warning("\n"); + } + return n; +} + +static void +packet_write(const void* p, unsigned size) +{ + unsigned n; + if (global_opts.debug_level >= DBGLVL_H) { + unsigned j; + warning(MYNAME ": pcktwr "); + for (j = 0; j < size; j++) { + warning("%02x ", ((gbuint8*)p)[j]); + } + warning("\n"); + } + n = delbin_os_ops.packet_write(p, size); + if (n != size) { + fatal(MYNAME ": short write %u %u\n", size, n); + } +} + +//----------------------------------------------------------------------------- + +// dynamically sized buffer with space reserved for message header and trailer +typedef struct { + // message data size + unsigned size; + // buffer size + unsigned capacity; + gbuint8* buf; + // convenience pointer to message data area + void* data; +} message_t; + +static void +message_init(message_t* m) +{ + m->capacity = 100; + m->buf = xmalloc(m->capacity); + m->data = m->buf + 2 + 8; +} + +static void +message_init_size(message_t* m, unsigned size) +{ + m->size = size; + m->capacity = 2 + 8 + size + 4; + m->buf = xmalloc(m->capacity); + m->data = m->buf + 2 + 8; +} + +static void +message_free(message_t* m) +{ + xfree(m->buf); + m->buf = NULL; + m->data = NULL; +} + +static void +message_ensure_size(message_t* m, unsigned size) +{ + m->size = size; + if (m->capacity < 2 + 8 + size + 4) { + m->capacity = 2 + 8 + size + 4; + xfree(m->buf); + m->buf = xmalloc(m->capacity); + m->data = m->buf + 2 + 8; + } +} + +static unsigned +message_get_id(const message_t* m) +{ + return le_readu16(m->buf + 4); +} + +//----------------------------------------------------------------------------- + +static void +message_write(unsigned msg_id, message_t* m) +{ + unsigned chksum; + unsigned count; + unsigned n; + gbuint8* p = m->buf; + + // header (2 start bytes filled in later) + p[2] = 0xdb; + p[3] = 0xfe; + le_write16(p + 4, msg_id); + // "data size" includes 4 trailer bytes + le_write16(p + 6, m->size + 4); + chksum = checksum(p + 2, 6); + le_write16(p + 8, chksum); + // message data (filled in by caller) + chksum = checksum(m->data, m->size); + n = 2 + 8 + m->size; + // trailer (checksum and marker bytes) + le_write16(p + n, chksum); + p[n + 2] = 0xad; + p[n + 3] = 0xbc; + // size of message not counting packet start bytes + count = 8 + m->size + 4; + do { + const gbuint8 save0 = p[0]; + const gbuint8 save1 = p[1]; + n = delbin_os_packet_size - 2; + if (n > count) { + n = count; + } + // doc. says 0x20, device sends 0, probably ignored + p[0] = 0x20; + // valid bytes in packet after first 2 + p[1] = n; + packet_write(p, 2 + n); + p[0] = save0; + p[1] = save1; + p += n; + count -= n; + } while (count != 0); + if (global_opts.debug_level >= DBGLVL_M) + warning(MYNAME ": sent %x\n", msg_id); +} + +// Get one valid message. +// If a corrupted message with the right id is seen, return failure (0). +static unsigned +message_read_1(unsigned msg_id, message_t* m) +{ + gbuint8 buf[256]; + gbuint8* p; + unsigned total = 0; + unsigned count = 0; + unsigned id = 0; + + for (;;) { + for (;;) { + unsigned n = packet_read(buf); + if (n >= 10 && buf[2] == 0xdb && buf[3] == 0xfe && + checksum(buf + 2, 6) == le_readu16(buf + 8)) + { + count = buf[1] - 8; + total = le_readu16(buf + 6); + id = le_readu16(buf + 4); + message_ensure_size(m, total - 4); + memcpy(m->buf, buf, 2 + buf[1]); + break; + } + } + while (count < total && buf[1] == delbin_os_packet_size - 2) { + unsigned n; + packet_read(buf); + n = buf[1]; + if (n > total - count) { + n = total - count; + } + memcpy((char*)m->data + count, buf + 2, n); + count += n; + } + p = (gbuint8*)m->data + m->size; + if (checksum(m->data, m->size) == le_readu16(p) && + p[2] == 0xad && p[3] == 0xbc) + { + if (global_opts.debug_level >= DBGLVL_M) + warning(MYNAME ": received %x\n", id); + break; + } + if (global_opts.debug_level >= DBGLVL_M) + warning(MYNAME ": corrupted message %x\n", id); + if (id == msg_id) { + id = 0; + break; + } + } + return id; +} + +// Send MSG_ACK for given message +static void +message_ack(unsigned id, const message_t* m) +{ + message_t ack; + char* p1; + const char* p2 = m->data; + switch (id) { + case MSG_ACK: + case MSG_NACK: + case MSG_NAVIGATION: + case MSG_SATELLITE_INFO: + // don't ack these + return; + } + message_init_size(&ack, 4); + p1 = ack.data; + // ack payload is id and body checksum of acked message + le_write16(p1, id); + p1[2] = p2[m->size]; + p1[3] = p2[m->size + 1]; + message_write(MSG_ACK, &ack); + message_free(&ack); +} + +// Get specific message, ignoring others. Sends ACK for non-interval messages. +// Gives up if 6 navigation messages are received, which means we waited at least +// 5 seconds. +static int +message_read(unsigned msg_id, message_t* m) +{ + unsigned id; + int interval_message_count = 0; + + if (global_opts.debug_level >= DBGLVL_M) + warning(MYNAME ": looking for %x\n", msg_id); + for (;;) { + id = message_read_1(msg_id, m); + if (id == 0) { + break; + } + message_ack(id, m); + if (id == msg_id) { + break; + } + if (id == MSG_NAVIGATION) { + interval_message_count++; + if (interval_message_count == 6) { + break; + } + } + } + return id == msg_id; +} + +// Read a sequence of messages, up to a MSG_TRANSFER_COMPLETE +static int +get_batch(message_t** array, unsigned* n) +{ + int success = 1; + unsigned array_max = 100; + message_t* a = xmalloc(array_max * sizeof(message_t)); + unsigned i = 0; + unsigned id; + if (global_opts.debug_level >= DBGLVL_M) + warning(MYNAME ": begin get_batch\n"); + do { + unsigned timeout_count = 0; + if (i == array_max) { + message_t* old_a = a; + array_max += array_max; + a = xmalloc(array_max * sizeof(message_t)); + memcpy(a, old_a, i * sizeof(message_t)); + xfree(old_a); + } + message_init(&a[i]); + for (;;) { + id = message_read_1(0, &a[i]); + switch (id) { + case MSG_NAVIGATION: + timeout_count++; + if (timeout_count == 6) { + success = 0; + break; + } + // fall through + case MSG_ACK: + case MSG_NACK: + case MSG_SATELLITE_INFO: + continue; + } + break; + } + message_ack(id, &a[i]); + i++; + } while (success && id != MSG_TRANSFER_COMPLETE); + if (success) { + *array = a; + *n = i - 1; + message_free(&a[*n]); + if (global_opts.debug_level >= DBGLVL_M) + warning(MYNAME ": end get_batch, %u messages\n", *n); + } else { + while (i--) { + message_free(&a[i]); + } + xfree(a); + *array = NULL; + *n = 0; + if (global_opts.debug_level >= DBGLVL_M) + warning(MYNAME ": end get_batch, failed\n"); + } + return success; +} + +static struct { + unsigned msg_id; + message_t msg; +} *batch_array; +static unsigned batch_array_max; +static unsigned batch_array_i; + +// add a message to sequence that will later be sent all at once +static void +add_to_batch(unsigned id, message_t* m) +{ + if (batch_array_i == batch_array_max) { + void* old = batch_array; + if (batch_array_max == 0) { + batch_array_max = 50; + } + batch_array_max += batch_array_max; + batch_array = xmalloc(batch_array_max * sizeof(*batch_array)); + if (batch_array_i) { + memcpy(batch_array, old, batch_array_i * sizeof(*batch_array)); + xfree(old); + } + } + batch_array[batch_array_i].msg_id = id; + batch_array[batch_array_i].msg = *m; + batch_array_i++; + memset(m, 0, sizeof(*m)); +} + +// send an accumulated sequence of messages +static void +send_batch(int expect_transfer_complete) +{ + message_t m; + const unsigned n = batch_array_i; + unsigned i; + + message_init(&m); + if (global_opts.debug_level >= DBGLVL_M) + warning(MYNAME ": begin send_batch, %u messages\n", n); + for (i = 0; i < n; i++) { + unsigned timeout_count = 0; + int nack = 0; + message_write(batch_array[i].msg_id, &batch_array[i].msg); + for (;;) { + unsigned id = message_read_1(0, &m); + switch (id) { + case MSG_ACK: + if (nack) gb_sleep(100000); + break; + case MSG_NAVIGATION: + timeout_count++; + if (timeout_count > 2) { + fatal(MYNAME ": send_batch timed out\n"); + } + if (timeout_count == 2) { + if (global_opts.debug_level >= DBGLVL_M) + warning(MYNAME ": re-sending %x\n", batch_array[i].msg_id); + message_write(batch_array[i].msg_id, &batch_array[i].msg); + } + // fall through + case MSG_NACK: + case MSG_SATELLITE_INFO: + continue; + default: + warning(MYNAME ": unexpected response message %x during send_batch\n", id); + continue; + } + break; + } + } + if (expect_transfer_complete) { + message_read(MSG_TRANSFER_COMPLETE, &m); + } + if (global_opts.debug_level >= DBGLVL_M) + warning(MYNAME ": end send_batch\n"); + for (i = n; i--;) { + message_free(&batch_array[i].msg); + } + xfree(batch_array); + message_free(&m); + batch_array_i = batch_array_max = 0; +} + +//----------------------------------------------------------------------------- +// Coordinate conversion + +static double +delbin_rad2deg(gbint32 x) +{ + return x * ((180 / M_PI) / 100000000); +} + +static gbint32 +delbin_deg2rad(double x) +{ + return (gbint32)(x * ((M_PI / 180) * 100000000)); +} + +//----------------------------------------------------------------------------- +// Waypoint reading + +static time_t +decode_time(const gbuint8* p) +{ + struct tm t; + t.tm_year = p[0]; + t.tm_mon = p[1] - 1; + t.tm_mday = p[2]; + t.tm_hour = p[3]; + t.tm_min = p[4]; + t.tm_sec = p[5]; + return mkgmtime(&t); +} + +static waypoint* +decode_waypoint(const void* data) +{ + waypoint* wp = waypt_new(); + const msg_waypoint_t* p = data; + const char* s; + float f; + + wp->creation_time = decode_time(&p->year); + wp->latitude = delbin_rad2deg(le_read32(p->latitude)); + wp->longitude = delbin_rad2deg(le_read32(p->longitude)); + f = le_read_float(p->elevation); + if (f > UNKNOWN_ELEV) { + wp->altitude = f; + } + wp->icon_descr = waypoint_symbol(p->symbol); + if (wp->icon_descr) { + wp->icon_descr = xstrdup(wp->icon_descr); + } + if (p->name_size && p->name[0]) { + wp->description = xstrdup(p->name); + } + s = p->name + p->name_size; + if (le_readu16(s) && s[2]) { + wp->notes = xstrdup(s + 2); + } + return wp; +} + +static void +read_waypoints(void) +{ + message_t m; + message_t* msg_array; + unsigned msg_array_n; + waypoint* wp = NULL; + unsigned n_point; + unsigned notes_i = 0; + unsigned notes_max = 0; + unsigned i; + int attempt = ATTEMPT_MAX; + + message_init(&m); + // get number of waypoints + for (;;) { + m.size = 0; + message_write(MSG_WAYPOINT_COUNT, &m); + if (message_read(MSG_WAYPOINT_COUNT, &m)) + break; + if (--attempt == 0) + fatal(MYNAME ": reading waypoint count failed\n"); + } + n_point = le_readu32(m.data); + if (global_opts.debug_level >= DBGLVL_L) + warning(MYNAME ": %u waypoints\n", n_point); + if (n_point == 0) { + message_free(&m); + return; + } + // get waypoint messages + attempt = ATTEMPT_MAX; + for (;;) { + m.size = MSG_REQUEST_WAYPOINTS_SIZE; + memset(m.data, 0, m.size); + // This byte is documented as reserved. Setting it to 3 is required to get + // extended notes (message 0xb015) with PN-40 firmware 2.5. + // Whether it has any effect with earlier firmware or the PN-20 is unknown. + ((char*)m.data)[1] = 3; + message_write(MSG_REQUEST_WAYPOINTS, &m); + if (get_batch(&msg_array, &msg_array_n)) + break; + if (--attempt == 0) + fatal(MYNAME ": reading waypoints failed\n"); + if (global_opts.debug_level >= DBGLVL_M) + warning(MYNAME ": timed out reading waypoints, retrying\n"); + m.size = MSG_BREAK_SIZE; + memset(m.data, 0, m.size); + message_write(MSG_BREAK, &m); + } + message_free(&m); + // process waypoint messages + for (i = 0; i < msg_array_n; i++) { + unsigned id = message_get_id(&msg_array[i]); + if (id == MSG_WAYPOINT_OUT) { + wp = decode_waypoint(msg_array[i].data); + waypt_add(wp); + notes_i = 0; + notes_max = 0; + if (global_opts.debug_level >= DBGLVL_L) + warning(MYNAME ": read waypoint '%s'\n", wp->description); + } else if (wp && id == MSG_WAYPOINT_NOTE_OUT) { + const msg_waypoint_note_t* p = msg_array[i].data; + const char* s = p->name + p->name_size; + unsigned nn = le_readu16(s); + if (notes_max < notes_i + nn) { + char* old = wp->notes; + if (notes_max == 0) { + notes_max = nn; + } + do { + notes_max += notes_max; + } while (notes_max < notes_i + nn); + wp->notes = xmalloc(notes_max); + if (old) { + memcpy(wp->notes, old, notes_i); + xfree(old); + } + } + if (nn) { + memcpy(wp->notes + notes_i, s + 2, nn); + notes_i += nn; + if (wp->notes[notes_i - 1] == 0) { + notes_i--; + } + } + } else { + fatal(MYNAME ": unexpected message %x while reading waypoints\n", id); + } + message_free(&msg_array[i]); + } + xfree(msg_array); +} + +//----------------------------------------------------------------------------- +// Waypoint writing + +static void +encode_time(time_t time_, gbuint8* p) +{ + const struct tm* t = gmtime(&time_); + p[0] = t->tm_year; + p[1] = t->tm_mon + 1; + p[2] = t->tm_mday; + p[3] = t->tm_hour; + p[4] = t->tm_min; + p[5] = t->tm_sec; +} + +static void +get_gc_notes(const waypoint* wp, int* symbol, char** notes, unsigned* notes_size) +{ + fs_xml* fs_gpx; + xml_tag* root = NULL; + gbfile* fd = gbfopen(NULL, "w", MYNAME); + const char* size = NULL; + int gc_sym = 0; + + switch (wp->gc_data->type) { + case gt_traditional: gc_sym = 160; break; + case gt_multi: gc_sym = 161; break; + case gt_virtual: gc_sym = 169; break; + case gt_letterbox: gc_sym = 163; break; + case gt_event: gc_sym = 165; break; + case gt_suprise: gc_sym = 162; break; + case gt_webcam: gc_sym = 170; break; + case gt_earth: gc_sym = 168; break; + case gt_benchmark: gc_sym = 172; break; + case gt_cito: gc_sym = 167; break; + case gt_mega: gc_sym = 166; break; + case gt_unknown: + case gt_locationless: + case gt_ape: + break; + } + if (wp->description) { + gbfputs(wp->description, fd); + if (wp->gc_data->placer) { + gbfprintf(fd, " by %s", wp->gc_data->placer); + } + gbfputc('\n', fd); + } + gbfprintf(fd, "Cache ID: %s\n", wp->shortname); + if (gc_sym) { + gbfprintf(fd, "%s\n", waypoint_symbol(gc_sym)); + *symbol = gc_sym; + } else if (wp->icon_descr) { + gbfprintf(fd, "%s\n", wp->icon_descr); + } + switch (wp->gc_data->container) { + case gc_micro: size = "Micro"; break; + case gc_small: size = "Small"; break; + case gc_regular: size = "Regular"; break; + case gc_large: size = "Large"; break; + case gc_unknown: + case gc_other: + case gc_virtual: + break; + } + if (size) { + gbfprintf(fd, "SIZE: %s\n", size); + } + if (wp->gc_data->diff % 10) { + gbfprintf(fd, "D%.1f", wp->gc_data->diff / 10.0); + } else { + gbfprintf(fd, "D%u", wp->gc_data->diff / 10); + } + if (wp->gc_data->terr % 10) { + gbfprintf(fd, "/T%.1f\n", wp->gc_data->terr / 10.0); + } else { + gbfprintf(fd, "/T%u\n", wp->gc_data->terr / 10); + } + if (wp->gc_data->hint) { + gbfprintf(fd, "HINT: %s\n", wp->gc_data->hint); + } + if (wp->gc_data->desc_short.utfstring || wp->gc_data->desc_long.utfstring) { + gbfputs("DESC: ", fd); + if (wp->gc_data->desc_short.utfstring) { + char* s1 = strip_html(&wp->gc_data->desc_short); + char* s2 = cet_str_utf8_to_any(s1, global_opts.charset); + gbfprintf(fd, "%s\n", s2); + xfree(s2); + xfree(s1); + } + if (wp->gc_data->desc_long.utfstring) { + char* s1 = strip_html(&wp->gc_data->desc_long); + char* s2 = cet_str_utf8_to_any(s1, global_opts.charset); + gbfputs(s2, fd); + xfree(s2); + xfree(s1); + } + } + fs_gpx = (fs_xml*)fs_chain_find(wp->fs, FS_GPX); + if (opt_logs && fs_gpx && fs_gpx->tag) { + root = xml_findfirst(fs_gpx->tag, "groundspeak:logs"); + } + if (root) { + xml_tag* curlog = xml_findfirst(root, "groundspeak:log"); + if (curlog) { + gbfputs("\nLOG:\n", fd); + } + for (; curlog; curlog = xml_findnext(root, curlog, "groundspeak:log")) { + xml_tag* logpart = xml_findfirst(curlog, "groundspeak:type"); + if (logpart) { + gbfprintf(fd, "%s\n", logpart->cdata); + } + logpart = xml_findfirst(curlog, "groundspeak:date"); + if (logpart) { + time_t logtime = xml_parse_time(logpart->cdata, NULL); + const struct tm* logtm = gmtime(&logtime); + gbfprintf(fd, "%d-%d-%d ", logtm->tm_year + 1900, logtm->tm_mon + 1, logtm->tm_mday); + } + logpart = xml_findfirst(curlog, "groundspeak:finder"); + if (logpart) { + char* s = cet_str_utf8_to_any(logpart->cdata, global_opts.charset); + gbfputs(s, fd); + xfree(s); + } + logpart = xml_findfirst(curlog, "groundspeak:text"); + if (logpart) { + char* s = cet_str_utf8_to_any(logpart->cdata, global_opts.charset); + gbfprintf(fd, ", %s", s); + xfree(s); + } + gbfputc('\n', fd); + } + } + gbfputc(0, fd); + *notes_size = fd->memlen; + *notes = xmalloc(*notes_size); + memcpy(*notes, fd->handle.mem, *notes_size); + gbfclose(fd); +} + +static void +write_waypoint_notes(const char* notes, unsigned size, const char* name) +{ + message_t m; + const unsigned name_size = strlen(name) + 1; + const unsigned bytes_per_msg = (10 * (delbin_os_packet_size - 2)) - name_size - 20; + const unsigned msg_count = (size + (bytes_per_msg - 1)) / bytes_per_msg; + unsigned i = 1; + + do { + char* pp; + unsigned n = bytes_per_msg; + msg_waypoint_note_t* p; + message_init_size(&m, 2 + 2 + 1 + name_size + 2 + bytes_per_msg); + p = m.data; + le_write16(p->index, i++); + le_write16(p->total, msg_count); + p->name_size = name_size; + memcpy(p->name, name, p->name_size); + pp = p->name + p->name_size; + if (n > size) { + n = size; + } + le_write16(pp, n); + pp += 2; + memcpy(pp, notes, n); + pp += n; + if (*(pp - 1)) { + *pp++ = 0; + } + notes += n; + size -= n; + m.size = pp - (char*)p; + add_to_batch(MSG_WAYPOINT_NOTE_IN, &m); + } while (size != 0); +} + +static void +write_waypoint(const waypoint* wp) +{ + message_t m; + msg_waypoint_t* p; + const char* name = wp->shortname; + unsigned name_size; + char* notes; + unsigned notes_size = 0; + unsigned extended_notes_size = 0; + char* notes_freeable = NULL; + int symbol = -1; + float elev = UNKNOWN_ELEV; + char* pp; + + if (waypt_empty_gc_data(wp)) { + notes = wp->notes; + if (notes == NULL && wp->description && strcmp(wp->shortname, wp->description)) { + notes = wp->description; + } + if (notes) { + notes_size = strlen(notes) + 1; + } + } else { + get_gc_notes(wp, &symbol, ¬es, ¬es_size); + notes_freeable = notes; + if (wp->description) { + name = wp->description; + } + } + if (notes_size > 800) { + if (use_extended_notes) { + extended_notes_size = notes_size; + notes_size = 1; + } else { + notes_size = 800; + } + } + name_size = strlen(name) + 1; + if (name_size > 255) { + name_size = 255; + } + message_init_size(&m, 31 + name_size + notes_size); + p = m.data; + + waypoint_i++; + le_write32(p->total, waypoint_n); + le_write32(p->index, waypoint_i); + encode_time(wp->creation_time, &p->year); + le_write32(p->latitude, delbin_deg2rad(wp->latitude)); + le_write32(p->longitude, delbin_deg2rad(wp->longitude)); + if (wp->altitude > unknown_alt) { + elev = wp->altitude; + } + le_write_float(p->elevation, elev); + if (symbol < 0) { + symbol = 0; + if (wp->icon_descr) { + symbol = waypoint_symbol_index(wp->icon_descr); + } + } + p->symbol = symbol; + p->name_size = name_size; + memcpy(p->name, name, name_size - 1); + p->name[name_size - 1] = 0; + pp = p->name + name_size; + m.size = (pp + 2 + notes_size) - (char*)p; + if (extended_notes_size) { + le_write16(pp, 0xffff); + pp[2] = 0; + } else { + le_write16(pp, notes_size); + if (notes) { + memcpy(pp + 2, notes, notes_size - 1); + pp[2 + notes_size - 1] = 0; + } + } + + add_to_batch(MSG_WAYPOINT_IN, &m); + + if (extended_notes_size) { + write_waypoint_notes(notes, extended_notes_size, name); + } + if (notes_freeable) { + xfree(notes_freeable); + } + if (global_opts.debug_level >= DBGLVL_L) + warning(MYNAME ": wrote waypoint %u '%s'\n", waypoint_i, name); +} + +static void +write_waypoints(void) +{ + waypoint_i = 0; + waypoint_n = waypt_count(); + waypt_disp_all(write_waypoint); + send_batch(TRUE); +} + +//----------------------------------------------------------------------------- +// Track reading + +static void +decode_sat_fix(waypoint* wp, const gbuint8 status) +{ + switch (status & 3) { + case 1: wp->fix = fix_none; break; + case 2: wp->fix = fix_2d; break; + case 3: + wp->fix = fix_3d; + if (status & 4) { + wp->fix = fix_dgps; + } + break; + } +} + +static void +decode_track_point(const void* data, unsigned* wp_array_i, unsigned max_point) +{ + const msg_track_point_t* p = data; + const unsigned n = p->number; + unsigned i; + unsigned j = *wp_array_i; + + if (j + n > max_point) { + fatal(MYNAME ": read too many track points\n"); + } + for (i = 0; i < n; i++, j++) { + waypoint* wp = waypt_new(); + float elev = le_read_float(p->point[i].elevation); + wp_array[j] = wp; + wp->creation_time = decode_time(&p->point[i].year); + wp->latitude = delbin_rad2deg(le_read32(p->point[i].latitude)); + wp->longitude = delbin_rad2deg(le_read32(p->point[i].longitude)); + if (elev > UNKNOWN_ELEV) { + wp->altitude = elev; + } + wp->speed = le_readu16(p->point[i].speed); + wp->speed *= (100.0f / (60 * 60)); + wp->wpt_flags.speed = 1; + decode_sat_fix(wp, p->point[i].status); + // use microseconds as track segment marker + wp->microseconds = p->point[i].status & 0x10; + } + *wp_array_i = j; +} + +static void +read_track(route_head* track) +{ + message_t m; + message_t* msg_array; + const msg_track_header_t* p; + unsigned msg_array_n; + unsigned wp_array_i = 0; + unsigned n_point; + unsigned segment = 1; + char* track_name = NULL; + unsigned i; + int attempt = ATTEMPT_MAX; + + message_init(&m); + // read track messages + for (;;) { + m.size = MSG_REQUEST_TRACKS_SIZE; + memset(m.data, 0, m.size); + ((char*)m.data)[0] = 1; // Download single track + strcpy((char*)m.data + 1, track->rte_name); + message_write(MSG_REQUEST_TRACKS, &m); + if (get_batch(&msg_array, &msg_array_n)) + break; + if (--attempt == 0) + fatal(MYNAME ": reading track '%s' failed\n", track->rte_name); + if (global_opts.debug_level >= DBGLVL_M) + warning(MYNAME ": timed out reading track '%s', retrying\n", track->rte_name); + m.size = MSG_BREAK_SIZE; + memset(m.data, 0, m.size); + message_write(MSG_BREAK, &m); + } + message_free(&m); + if (msg_array_n == 0 || message_get_id(&msg_array[0]) != MSG_TRACK_HEADER_OUT) { + fatal(MYNAME ": reading track '%s' failed (missing track header)\n", track->rte_name); + } + // process track messages + p = msg_array[0].data; + if (le_readu16(p->comment_size)) { + track->rte_desc = xstrdup(p->comment); + } + track->line_color.bbggrr = track_color(p->color[0]); + n_point = le_readu32(p->total_points); + wp_array = xcalloc(n_point, sizeof(*wp_array)); + message_free(&msg_array[0]); + for (i = 1; i < msg_array_n; i++) { + unsigned id = message_get_id(&msg_array[i]); + if (id == MSG_TRACK_POINT_OUT) { + decode_track_point(msg_array[i].data, &wp_array_i, n_point); + } else { + fatal(MYNAME ": unexpected message %x while reading track '%s'\n", id, track->rte_name); + } + message_free(&msg_array[i]); + } + xfree(msg_array); + if (n_point != wp_array_i) { + fatal(MYNAME ": track point count mismatch, expected %u, got %u\n", n_point, wp_array_i); + } + if (global_opts.debug_level >= DBGLVL_L) + warning(MYNAME ": read track '%s' %u points\n", track->rte_name, n_point); + // make track (one for each segment) + track_add_wpt(track, wp_array[0]); + for (i = 1; i < n_point; i++) { + // if track segment marker + if (wp_array[i]->microseconds) { + const char* desc = track->rte_desc; + if (track_name == NULL) { + // save original name, append " #1" to first segment name + track_name = track->rte_name; + track->rte_name = xmalloc(strlen(track_name) + 4); + sprintf(track->rte_name, "%s #1", track_name); + } + // make a new track for segment + segment++; + track_add_head(track); + track = route_head_alloc(); + track->rte_name = xmalloc(strlen(track_name) + 7); + sprintf(track->rte_name, "%s #%u", track_name, segment); + track->rte_desc = xstrdup(desc); + } + wp_array[i]->microseconds = 0; + track_add_wpt(track, wp_array[i]); + } + track_add_head(track); + if (track_name) { + xfree(track_name); + } + xfree(wp_array); +} + +static void +read_tracks(void) +{ + message_t m; + message_t* msg_array; + unsigned msg_array_n; + route_head** track_array; + unsigned total; + unsigned i; + int attempt = ATTEMPT_MAX; + + message_init(&m); + // get number of tracks + for (;;) { + m.size = 0; + message_write(MSG_TRACK_COUNT, &m); + if (message_read(MSG_TRACK_COUNT, &m)) + break; + if (--attempt == 0) + fatal(MYNAME ": reading track count failed\n"); + } + total = le_readu32(m.data); + if (global_opts.debug_level >= DBGLVL_L) + warning(MYNAME ": %u tracks\n", total); + if (total == 0) { + message_free(&m); + return; + } + + // First get track headers, then request each track with non-zero number of points + attempt = ATTEMPT_MAX; + for (;;) { + m.size = MSG_REQUEST_TRACKS_SIZE; + memset(m.data, 0, m.size); + ((char*)m.data)[0] = 2; // Download all track headers + message_write(MSG_REQUEST_TRACKS, &m); + if (get_batch(&msg_array, &msg_array_n)) + break; + if (--attempt == 0) + fatal(MYNAME ": reading track headers failed\n"); + if (global_opts.debug_level >= DBGLVL_M) + warning(MYNAME ": timed out reading track headers, retrying\n"); + m.size = MSG_BREAK_SIZE; + memset(m.data, 0, m.size); + message_write(MSG_BREAK, &m); + } + message_free(&m); + track_array = xcalloc(total, sizeof(*track_array)); + for (i = 0; i < msg_array_n; i++) { + unsigned id = message_get_id(&msg_array[i]); + if (id == MSG_TRACK_HEADER_OUT) { + const msg_track_header_t* p = msg_array[i].data; + if (le_readu32(p->total_points)) { + track_array[i] = route_head_alloc(); + track_array[i]->rte_name = xstrdup(p->name); + } + } else { + fatal(MYNAME ": unexpected message %x while reading track headers\n", id); + } + message_free(&msg_array[i]); + } + xfree(msg_array); + // get each track + for (i = 0; i < total; i++) { + if (track_array[i]) { + read_track(track_array[i]); + } + } + xfree(track_array); +} + +//----------------------------------------------------------------------------- +// Track writing + +static void +write_track_points(void) +{ + message_t m; + const unsigned pt_per_msg = 10; + msg_track_point_t* p = NULL; + unsigned i = 0; + unsigned j = 0; + + do { + const waypoint* wp = wp_array[i]; + float f; + + if (j == 0) { + message_init_size(&m, 9 + 23 * pt_per_msg); + p = m.data; + le_write32(p->total, waypoint_n); + le_write32(p->index, i + 1); + } + assert(p); + encode_time(wp->creation_time, &p->point[j].year); + le_write32(p->point[j].latitude, delbin_deg2rad(wp->latitude)); + le_write32(p->point[j].longitude, delbin_deg2rad(wp->longitude)); + f = UNKNOWN_ELEV; + if (wp->altitude > unknown_alt) { + f = wp->altitude; + } + le_write_float(p->point[j].elevation, f); + f = WAYPT_GET(wp, speed, 0); + f *= (60 * 60) / 100; + le_write16(p->point[j].speed, (gbuint16)f); + f = WAYPT_GET(wp, course, 0); + f *= 100; + le_write16(p->point[j].heading, (gbuint16)f); + switch (wp->fix) { + default: p->point[j].status = 0; break; + case fix_none: p->point[j].status = 1; break; + case fix_2d: p->point[j].status = 2; break; + case fix_3d: p->point[j].status = 3; break; + case fix_dgps: p->point[j].status = 4 | 3; break; + } + i++; + j++; + if (j == pt_per_msg || i == waypoint_n) { + p->number = j; + m.size = 9 + 23 * j; + add_to_batch(MSG_TRACK_POINT_IN, &m); + j = 0; + } + } while (i < waypoint_n); +} + +static void +write_track_begin(const route_head* track) +{ + waypoint_i = 0; + waypoint_n = track->rte_waypt_ct; + if (waypoint_n) { + wp_array = xmalloc(waypoint_n * sizeof(*wp_array)); + } +} + +static void +write_track_point(const waypoint* wp) +{ + wp_array[waypoint_i++] = (waypoint*)wp; +} + +static void +write_track_end(const route_head* track) +{ + message_t m; + msg_track_header_in_t* p; + unsigned comment_size = 0; + + if (waypoint_n == 0) { + return; + } + if (track->rte_desc) { + comment_size = strlen(track->rte_desc) + 1; + } + message_init_size(&m, sizeof(msg_track_header_in_t) - 1 + comment_size); + p = m.data; + memset(p->name, 0, sizeof(p->name)); + if (track->rte_name) { + strncpy(p->name, track->rte_name, sizeof(p->name) - 1); + } else { + sprintf(p->name, "%lu", (long)wp_array[0]->creation_time); + } + le_write32(p->total_points, waypoint_n); + encode_time(current_time(), &p->year); + le_write16(p->color, track_color_index(track->line_color.bbggrr)); + le_write16(p->comment_size, comment_size); + if (comment_size) { + memcpy(p->comment, track->rte_desc, comment_size); + } + add_to_batch(MSG_TRACK_HEADER_IN, &m); + write_track_points(); + send_batch(FALSE); + xfree(wp_array); +} + +static void +write_tracks(void) +{ + track_disp_all(write_track_begin, write_track_end, write_track_point); +} + +//----------------------------------------------------------------------------- +// Route reading + +static void +decode_route_shape(const void* data, unsigned* wp_array_i) +{ + const msg_route_shape_t* p = data; + const unsigned n = p->number; + unsigned i; + unsigned j = *wp_array_i; + + for (i = 0; i < n; i++, j++) { + char buf[32]; + waypoint* wp = waypt_new(); + wp_array[j] = wp; + wp->latitude = delbin_rad2deg(le_read32(p->point[i].latitude)); + wp->longitude = delbin_rad2deg(le_read32(p->point[i].longitude)); + sprintf(buf, "SHP%03u", j); + wp->shortname = xstrdup(buf); + } + *wp_array_i = j; +} + +static waypoint* +decode_route_point(const void* data) +{ + const msg_route_point_t* p = data; + const char* s = NULL; + gbfile* fd = gbfopen(NULL, "w", MYNAME); + waypoint* wp = waypt_new(); + if (p->name[0]) { + wp->shortname = xstrdup(p->name); + } + // give these a higher priority than the shape points + wp->route_priority = 1; + wp->latitude = delbin_rad2deg(le_read32(p->latitude)); + wp->longitude = delbin_rad2deg(le_read32(p->longitude)); + switch (p->itinerary_type) { + case 1: s = "Start"; break; + case 2: s = "Stop"; break; + case 3: s = "Finish"; break; + case 4: s = "Via"; break; + case 5: s = "Via Hidden"; break; + case 6: + switch (p->turn_type) { + case 1: s = "Turn, Straight"; break; + case 2: s = "Turn, Right"; break; + case 3: s = "Turn, Bear Right"; break; + case 4: s = "Turn, Keep Right"; break; + case 5: s = "Turn, Left"; break; + case 6: s = "Turn, Bear Left"; break; + case 7: s = "Turn, Keep Left"; break; + case 8: s = "Turn, Reverse Direction"; break; + case 9: s = "Turn, Street Name Change"; break; + } + break; + } + if (s) { + gbfprintf(fd, "Type: %s", s); + } + if (p->exit_label_size && p->exit_label[0]) { + gbfprintf(fd, "\nExit: %s", p->exit_label); + } + s = p->exit_label + p->exit_label_size; + if (s[0] && s[1]) { + gbfprintf(fd, "\n%s", s + 1); + } + if (fd->memlen) { + gbfputc(0, fd); + wp->notes = xmalloc(fd->memlen); + memcpy(wp->notes, fd->handle.mem, fd->memlen); + } + gbfclose(fd); + return wp; +} + +static void +read_route(route_head* route) +{ + message_t m; + message_t* msg_array; + const msg_route_header_t* p; + unsigned msg_array_n; + unsigned wp_array_i = 0; + unsigned route_total, shape_total, total; + unsigned i; + int attempt = ATTEMPT_MAX; + + message_init(&m); + for (;;) { + m.size = MSG_REQUEST_ROUTES_SIZE; + memset(m.data, 0, m.size); + ((char*)m.data)[0] = 1; // Download single route + strcpy((char*)m.data + 1, route->rte_name); + message_write(MSG_REQUEST_ROUTES, &m); + if (get_batch(&msg_array, &msg_array_n)) + break; + if (--attempt == 0) + fatal(MYNAME ": reading route '%s' failed (timed out)\n", route->rte_name); + if (global_opts.debug_level >= DBGLVL_M) + warning(MYNAME ": timed out reading route route '%s', retrying\n", route->rte_name); + m.size = MSG_BREAK_SIZE; + memset(m.data, 0, m.size); + message_write(MSG_BREAK, &m); + } + message_free(&m); + if (msg_array_n == 0 || message_get_id(&msg_array[0]) != MSG_ROUTE_HEADER_OUT) { + fatal(MYNAME ": missing route header\n"); + } + p = msg_array[0].data; + route_total = le_readu32(p->total_route_point); + shape_total = le_readu32(p->total_shape_point); + total = route_total + shape_total; + wp_array = xcalloc(total, sizeof(*wp_array)); + if (global_opts.debug_level >= DBGLVL_L) { + warning(MYNAME ": route '%s' %u points, %u shape points\n", + route->rte_name, route_total, shape_total); + } + message_free(&msg_array[0]); + for (i = 1; i < msg_array_n; i++) { + unsigned id = message_get_id(&msg_array[i]); + if (id == MSG_ROUTE_POINT_OUT) { + wp_array[wp_array_i] = decode_route_point(msg_array[i].data); + if (global_opts.debug_level >= DBGLVL_L) + warning(MYNAME ": route point '%s'\n", wp_array[wp_array_i]->shortname); + wp_array_i++; + } else if (id == MSG_ROUTE_SHAPE_OUT) { + decode_route_shape(msg_array[i].data, &wp_array_i); + } else { + fatal(MYNAME ": unexpected message %x while reading route '%s'\n", id, route->rte_name); + } + message_free(&msg_array[i]); + } + xfree(msg_array); + if (total != wp_array_i) { + fatal(MYNAME ": route point count mismatch, expected %u, got %u\n", total, wp_array_i); + } + for (i = 0; i < total; i++) { + route_add_wpt(route, wp_array[i]); + } + xfree(wp_array); + route_add_head(route); +} + +static void +read_routes(void) +{ + message_t m; + message_t* msg_array; + unsigned msg_array_n; + route_head** route_array; + unsigned total; + unsigned i; + int attempt = ATTEMPT_MAX; + + message_init(&m); + // get number of routes + for (;;) { + m.size = 0; + message_write(MSG_ROUTE_COUNT, &m); + if (message_read(MSG_ROUTE_COUNT, &m)) + break; + if (--attempt == 0) + fatal(MYNAME ": reading route count failed\n"); + } + total = le_readu32(m.data); + if (global_opts.debug_level >= DBGLVL_L) + warning(MYNAME ": %u routes\n", total); + if (total == 0) { + message_free(&m); + return; + } + + // First get route headers, then request each route + attempt = ATTEMPT_MAX; + for (;;) { + m.size = MSG_REQUEST_ROUTES_SIZE; + memset(m.data, 0, m.size); + ((char*)m.data)[0] = 2; // Download all route headers + message_write(MSG_REQUEST_ROUTES, &m); + if (get_batch(&msg_array, &msg_array_n)) + break; + if (--attempt == 0) + fatal(MYNAME ": reading route headers failed\n"); + if (global_opts.debug_level >= DBGLVL_M) + warning(MYNAME ": timed out reading route headers, retrying\n"); + m.size = MSG_BREAK_SIZE; + memset(m.data, 0, m.size); + message_write(MSG_BREAK, &m); + } + message_free(&m); + route_array = xcalloc(total, sizeof(*route_array)); + for (i = 0; i < msg_array_n; i++) { + unsigned id = message_get_id(&msg_array[i]); + if (id == MSG_ROUTE_HEADER_OUT) { + route_array[i] = route_head_alloc(); + route_array[i]->rte_name = xstrdup(((msg_route_header_t*)msg_array[i].data)->name); + } else { + fatal(MYNAME ": unexpected message %x while reading route headers\n", id); + } + message_free(&msg_array[i]); + } + xfree(msg_array); + // get each route + for (i = 0; i < total; i++) { + read_route(route_array[i]); + } + xfree(route_array); +} + +//----------------------------------------------------------------------------- +// Route writing + +static unsigned route_point_n; +static unsigned shape_point_n; +static unsigned* shape_point_counts; + +static void +write_route_shape_points(waypoint** array, unsigned n) +{ + message_t m; + const unsigned pt_per_msg = 25; + msg_route_shape_t* p = NULL; + unsigned i = 0; + unsigned j = 0; + + do { + if (j == 0) { + message_init_size(&m, 10 + 8 * pt_per_msg); + p = m.data; + le_write32(p->total, n); + le_write32(p->index, i + 1); + p->reserved = 0; + } + assert(p); + le_write32(p->point[j].latitude, delbin_deg2rad(array[i]->latitude)); + le_write32(p->point[j].longitude, delbin_deg2rad(array[i]->longitude)); + i++; + j++; + if (j == pt_per_msg || i == n) { + p->number = j; + m.size = 10 + 8 * j; + add_to_batch(MSG_ROUTE_SHAPE_IN, &m); + j = 0; + } + } while (i < n); +} + +static void +write_route_points(void) +{ + unsigned route_point_i = 0; + unsigned i = 0; + + while (i < waypoint_n) { + message_t m; + unsigned shape_n; + const waypoint* wp = wp_array[i]; + msg_route_point_t* p; + char* s; + + message_init_size(&m, sizeof(msg_route_point_t) + 1 + 1 + 4); + p = m.data; + memset(m.data, 0, m.size); + route_point_i++; + shape_n = shape_point_counts[route_point_i]; + le_write32(p->total, route_point_n); + le_write32(p->index, route_point_i); + if (wp->shortname) { + strncpy(p->name, wp->shortname, sizeof(p->name) - 1); + } else { + sprintf(p->name, "RPT%u", route_point_i); + } + le_write32(p->latitude, delbin_deg2rad(wp->latitude)); + le_write32(p->longitude, delbin_deg2rad(wp->longitude)); + p->exit_label_size = 1; + s = p->exit_label + p->exit_label_size; + s[0] = 1; // comment size + le_write32(s + 2, shape_n); + if (route_point_i == 1) { + p->itinerary_type = 1; // start + } else if (route_point_i == route_point_n) { + p->itinerary_type = 3; // finish + } + add_to_batch(MSG_ROUTE_POINT_IN, &m); + i++; + if (shape_n) { + write_route_shape_points(&wp_array[i], shape_n); + i += shape_n; + } + } +} + +static void +write_route_begin(const route_head* track) +{ + waypoint_i = 0; + route_point_n = 0; + shape_point_n = 0; + waypoint_n = track->rte_waypt_ct; + if (waypoint_n) { + wp_array = xmalloc(waypoint_n * sizeof(*wp_array)); + shape_point_counts = xcalloc(waypoint_n, sizeof(*shape_point_counts)); + } +} + +static void +write_route_point(const waypoint* wp) +{ + const char* s = wp->shortname; + wp_array[waypoint_i++] = (waypoint*)wp; + if (s && s[0] == 'S' && s[1] == 'H' && s[2] == 'P' && s[3] >= '0' && s[3] <= '9') { + shape_point_n++; + shape_point_counts[route_point_n]++; + } else { + route_point_n++; + } +} + +static void +write_route_end(const route_head* route) +{ + message_t m; + msg_route_header_in_t* p; + + if (waypoint_n == 0) { + return; + } + message_init_size(&m, sizeof(msg_route_header_in_t)); + p = m.data; + memset(p->name, 0, sizeof(p->name)); + if (route->rte_name) { + strncpy(p->name, route->rte_name, sizeof(p->name) - 1); + } else { + sprintf(p->name, "%lu", (long)wp_array[0]->creation_time); + } + p->type = 0; + le_write32(p->total_route_point, route_point_n); + le_write32(p->total_shape_point, shape_point_n); + add_to_batch(MSG_ROUTE_HEADER_IN, &m); + write_route_points(); + send_batch(TRUE); + if (wp_array) { + xfree(wp_array); + xfree(shape_point_counts); + } +} + +static void +write_routes(void) +{ + route_disp_all(write_route_begin, write_route_end, write_route_point); +} + +//----------------------------------------------------------------------------- +// Current position + +static waypoint* +decode_navmsg(const void* data) +{ + waypoint* wp = waypt_new(); + const msg_navigation_t* p = data; + struct tm t; + + t.tm_year = le_readu16(p->year) - 1900; + t.tm_mon = p->month - 1; + t.tm_mday = p->day; + t.tm_hour = p->hour; + t.tm_min = p->minute; + t.tm_sec = p->second; + wp->creation_time = mkgmtime(&t); + wp->sat = p->satellites; + wp->latitude = le_read_double(p->latitude); + wp->longitude = le_read_double(p->longitude); + wp->altitude = le_read_double(p->elevation); + wp->speed = le_read_float(p->speed); + wp->speed *= (1000.0f / (60 * 60)); + wp->wpt_flags.speed = 1; + wp->course = le_readu16(p->heading); + wp->course /= 100; + wp->wpt_flags.course = 1; + decode_sat_fix(wp, p->fix_status); + wp->shortname = xstrdup("Position"); + return wp; +} + +static waypoint* +read_position(void) +{ + waypoint* wp; + message_t m; + + message_init(&m); + message_read(MSG_NAVIGATION, &m); + wp = decode_navmsg(m.data); + if (wp->fix > fix_none && + message_read_1(MSG_SATELLITE_INFO, &m) == MSG_SATELLITE_INFO) + { + const msg_satellite_t* p = m.data; + wp->hdop = le_readu16(p->hdop); + wp->hdop /= 100; + wp->vdop = le_readu16(p->vdop); + wp->vdop /= 100; + wp->pdop = le_readu16(p->pdop); + wp->pdop /= 100; + } + message_free(&m); + return wp; +} + +//----------------------------------------------------------------------------- + +static void +delbin_rw_init(const char *fname) +{ + message_t m; + + delbin_os_ops.init(fname); + + // Send a break to clear any state from a previous failure + message_init(&m); + m.size = MSG_BREAK_SIZE; + memset(m.data, 0, m.size); + message_write(MSG_BREAK, &m); + // get version info + m.size = 0; + message_write(MSG_VERSION, &m); + if (message_read(MSG_VERSION, &m)) { + const msg_version_t* p = m.data; + if (global_opts.debug_level >= DBGLVL_L) + warning(MYNAME ": device %s %s\n", p->product, p->firmware); + if (opt_long_notes) { + use_extended_notes = TRUE; + } else if (strstr(p->product, "PN-40")) { + // Don't know if pre-2.5 PN-40 firmware or PN-20 can handle 0xb016 message + use_extended_notes = p->firmware[0] > '2' || + (p->firmware[0] == '2' && p->firmware[2] >= '5'); + } + } + message_free(&m); +} + +static void +delbin_rw_deinit(void) +{ + delbin_os_ops.deinit(); +} + +static void +delbin_read(void) +{ + if (doing_wpts) { + if (opt_getposn) { + waypt_add(read_position()); + } else { + read_waypoints(); + } + } + if (doing_trks) { + read_tracks(); + } + if (doing_rtes) { + read_routes(); + } +} + +static void +delbin_write(void) +{ + if (doing_wpts) { + write_waypoints(); + } + if (doing_trks) { + write_tracks(); + } + if (doing_rtes) { + write_routes(); + } +} + +static waypoint* +delbin_rd_position(posn_status* status) +{ + waypoint* wp = read_position(); + if (wp == NULL) { + status->request_terminate = 1; + } + return wp; +} + +ff_vecs_t delbin_vecs = { + ff_type_serial, + FF_CAP_RW_ALL, + delbin_rw_init, + delbin_rw_init, + delbin_rw_deinit, + delbin_rw_deinit, + delbin_read, + delbin_write, + NULL, + delbin_args, + CET_CHARSET_LATIN1, 1, + { delbin_rw_init, delbin_rd_position, delbin_rw_deinit } +}; + +//============================================================================= +// OS device I/O implementations + +#define VENDOR_ID 0x1163 +#define PRODUCT_ID 0x2020 + +//----------------------------------------------------------------------------- +// Windows +#if _WIN32 + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include + +static HANDLE hid_handle; + +static void +win_os_init(const char* fname) +{ + GUID hid_guid; + HDEVINFO dev_info; + SP_DEVICE_INTERFACE_DATA dev_int_data; + PHIDP_PREPARSED_DATA hid_ppd; + HIDP_CAPS hid_caps; + const char* busy = ""; + unsigned i; + + hid_handle = INVALID_HANDLE_VALUE; + HidD_GetHidGuid(&hid_guid); + dev_info = SetupDiGetClassDevs(&hid_guid, NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); + if (dev_info == INVALID_HANDLE_VALUE) { + fatal(MYNAME ": SetupDiGetClassDevs failed %u\n", GetLastError()); + } + dev_int_data.cbSize = sizeof(dev_int_data); + for (i = 0; SetupDiEnumDeviceInterfaces(dev_info, NULL, &hid_guid, i, &dev_int_data); i++) { + union { + SP_DEVICE_INTERFACE_DETAIL_DATA detail_data; + char buf[300]; + } u; + u.detail_data.cbSize = sizeof(u.detail_data); + if (SetupDiGetDeviceInterfaceDetail(dev_info, + &dev_int_data, &u.detail_data, sizeof(u.buf), NULL, NULL)) + { + HANDLE h = CreateFile(u.detail_data.DevicePath, + FILE_READ_DATA | FILE_WRITE_DATA, 0, NULL, OPEN_EXISTING, 0, NULL); + if (h != INVALID_HANDLE_VALUE) { + HIDD_ATTRIBUTES hid_attr; + hid_attr.Size = sizeof(hid_attr); + if (HidD_GetAttributes(h, &hid_attr) && + hid_attr.VendorID == VENDOR_ID && hid_attr.ProductID == PRODUCT_ID) + { + hid_handle = h; + break; + } + CloseHandle(h); + } else if (GetLastError() == ERROR_SHARING_VIOLATION && + strstr(u.detail_data.DevicePath, "1163") && + strstr(u.detail_data.DevicePath, "2020")) + { + busy = " (device busy?)"; + } + } + } + SetupDiDestroyDeviceInfoList(dev_info); + if (hid_handle == INVALID_HANDLE_VALUE) { + fatal(MYNAME ": no DeLorme PN found%s\n", busy); + } + if (!HidD_GetPreparsedData(hid_handle, &hid_ppd)) { + fatal(MYNAME ": HidD_GetPreparsedData failed %u\n", GetLastError()); + } + if (!HidP_GetCaps(hid_ppd, &hid_caps)) { + fatal(MYNAME ": HidP_GetCaps failed %u\n", GetLastError()); + } + // report length includes report id + delbin_os_packet_size = hid_caps.InputReportByteLength - 1; + HidD_FreePreparsedData(hid_ppd); +} + +static void +win_os_deinit(void) +{ + CloseHandle(hid_handle); +} + +static unsigned +win_os_packet_read(void* buf) +{ + unsigned n; + char buf1[257]; + // first byte is report id + if (ReadFile(hid_handle, buf1, delbin_os_packet_size + 1, &n, NULL) == 0) { + fatal(MYNAME ": ReadFile failed %u\n", GetLastError()); + } + if (n > 0) { + n--; + } + memcpy(buf, buf1 + 1, n); + return n; +} + +static unsigned +win_os_packet_write(const void* buf, unsigned size) +{ + unsigned n; + char buf1[257]; + // first byte is report id + buf1[0] = 0; + memcpy(buf1 + 1, buf, size); + if (WriteFile(hid_handle, buf1, delbin_os_packet_size + 1, &n, NULL) == 0) { + fatal(MYNAME ": WriteFile failed %u\n", GetLastError()); + } + if (n > size) { + n = size; + } + return n; +} + +delbin_os_ops_t delbin_os_ops = { + win_os_init, + win_os_deinit, + win_os_packet_read, + win_os_packet_write +}; + +#endif // _WIN32 + +//----------------------------------------------------------------------------- +// libusb +#if HAVE_LIBUSB + +#include + +static struct usb_device* usb_dev; +static usb_dev_handle* usb_handle; +static int endpoint_in; +static int endpoint_out; + +static void +libusb_os_init(const char* fname) +{ + struct usb_bus* bus; + const struct usb_endpoint_descriptor* endpoint_desc; + + usb_init(); + usb_find_busses(); + usb_find_devices(); + for (bus = usb_busses; usb_dev == NULL && bus; bus = bus->next) { + struct usb_device* d; + for (d = bus->devices; d; d = d->next) { + if (d->descriptor.idVendor == VENDOR_ID && d->descriptor.idProduct == PRODUCT_ID) { + usb_dev = d; + break; + } + } + } + if (usb_dev == NULL) { + fatal(MYNAME ": no DeLorme PN found\n"); + } + usb_handle = usb_open(usb_dev); + if (usb_handle == NULL) { + fatal(MYNAME ": %s\n", usb_strerror()); + } + + // Device has 1 configuration, 1 interface, 2 interrupt endpoints + if (usb_claim_interface(usb_handle, 0) < 0) { +#if LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP + if (usb_detach_kernel_driver_np(usb_handle, 0) < 0) { + warning(MYNAME ": %s\n", usb_strerror()); + } + if (usb_claim_interface(usb_handle, 0) < 0) +#endif + { + const char* s = usb_strerror(); + usb_close(usb_handle); + fatal(MYNAME ": %s\n", s); + } + } + endpoint_desc = usb_dev->config[0].interface[0].altsetting[0].endpoint; + delbin_os_packet_size = endpoint_desc[0].wMaxPacketSize; + endpoint_in = endpoint_desc[0].bEndpointAddress; + endpoint_out = endpoint_desc[1].bEndpointAddress; + if ((endpoint_in & USB_ENDPOINT_DIR_MASK) == USB_ENDPOINT_OUT) { + int t = endpoint_in; + endpoint_in = endpoint_out; + endpoint_out = t; + } +} + +static void +libusb_os_deinit(void) +{ + usb_release_interface(usb_handle, 0); + usb_close(usb_handle); +} + +static unsigned +libusb_os_packet_read(void* buf) +{ + int n = usb_interrupt_read(usb_handle, endpoint_in, buf, delbin_os_packet_size, 2000); + if (n < 0) { + fatal(MYNAME ": %s\n", usb_strerror()); + } + return n; +} + +static unsigned +libusb_os_packet_write(const void* buf, unsigned size) +{ + int n = usb_interrupt_write(usb_handle, endpoint_out, (char*)buf, size, 2000); + if (n < 0) { + fatal(MYNAME ": %s\n", usb_strerror()); + } + return n; +} + +#if __linux +static const delbin_os_ops_t libusb_os_ops = +#else +delbin_os_ops_t delbin_os_ops = +#endif +{ + libusb_os_init, + libusb_os_deinit, + libusb_os_packet_read, + libusb_os_packet_write +}; + +#endif // HAVE_LIBUSB + +//----------------------------------------------------------------------------- +// Linux +#if __linux + +#include +#include +#include +#include +#include +#include +#include +#include + +static int fd_hidraw; +static int fd_hiddev; + +static int linuxhid_os_init_status; + +static void +linuxhid_os_init(const char* fname) +{ + struct hidraw_devinfo info; + struct hiddev_report_info rinfo; + struct hiddev_field_info finfo; + DIR* dir = NULL; + struct dirent* d; + + fd_hidraw = fd_hiddev = -1; + if (fname && memcmp(fname, "hid:", 4) == 0) { + const char* raw_name = fname + 4; + const char* dev_name = strchr(raw_name, ','); + if (dev_name == NULL) { + fatal(MYNAME ": missing hiddev\n"); + } + fd_hidraw = open(raw_name, O_RDONLY); + if (fd_hidraw < 0) { + fatal(MYNAME ": %s: %s\n", raw_name, strerror(errno)); + } + dev_name++; + fd_hiddev = open(dev_name, O_WRONLY); + if (fd_hiddev < 0) { + fatal(MYNAME ": %s: %s\n", dev_name, strerror(errno)); + } + } else { + dir = opendir("/dev"); + } + while (dir && (d = readdir(dir)) != NULL) { + if (strncmp(d->d_name, "hidraw", 6) == 0) { + int fd1, fd2; + char raw_name[32]; + char dev_name[32]; + sprintf(raw_name, "/dev/%s", d->d_name); + fd1 = open(raw_name, O_RDONLY); + if (fd1 < 0) { + if (global_opts.debug_level >= DBGLVL_M) + warning(MYNAME ": %s: %s\n", raw_name, strerror(errno)); + continue; + } + sprintf(dev_name, "/dev/usb/hiddev%s", raw_name + sizeof("/dev/hidraw") - 1); + fd2 = open(dev_name, O_WRONLY); + if (fd2 < 0 && errno == ENOENT) { + sprintf(dev_name, "/dev/hiddev%s", raw_name + sizeof("/dev/hidraw") - 1); + fd2 = open(dev_name, O_WRONLY); + } + if (fd2 < 0) { + if (global_opts.debug_level >= DBGLVL_M) + warning(MYNAME ": %s: %s\n", dev_name, strerror(errno)); + close(fd1); + continue; + } + if (ioctl(fd1, HIDIOCGRAWINFO, &info) == 0 && + info.vendor == VENDOR_ID && info.product == PRODUCT_ID) + { + fd_hidraw = fd1; + fd_hiddev = fd2; + break; + } + close(fd1); + close(fd2); + } + } + if (dir) { + closedir(dir); + } + if (fd_hidraw < 0) { + if (linuxhid_os_init_status == 0) + fatal(MYNAME ": no DeLorme PN found\n"); + return; + } + rinfo.report_type = HID_REPORT_TYPE_INPUT; + rinfo.report_id = HID_REPORT_ID_FIRST; + if (ioctl(fd_hiddev, HIDIOCGREPORTINFO, &rinfo) < 0) { + warning(MYNAME ": HIDIOCGREPORTINFO: %s\n", strerror(errno)); + if (linuxhid_os_init_status == 0) + exit(1); + return; + } + finfo.report_type = rinfo.report_type; + finfo.report_id = rinfo.report_id; + finfo.field_index = 0; + if (ioctl(fd_hiddev, HIDIOCGFIELDINFO, &finfo) < 0) { + warning(MYNAME ": HIDIOCGFIELDINFO: %s\n", strerror(errno)); + if (linuxhid_os_init_status == 0) + exit(1); + return; + } + delbin_os_packet_size = finfo.maxusage; + linuxhid_os_init_status = 0; +} + +static void +linuxhid_os_deinit(void) +{ + close(fd_hidraw); + close(fd_hiddev); +} + +static unsigned +linuxhid_os_packet_read(void* buf) +{ + int n = read(fd_hidraw, buf, delbin_os_packet_size); + if (n < 0) { + fatal(MYNAME ": %s\n", strerror(errno)); + } + return n; +} + +static unsigned +linuxhid_os_packet_write(const void* buf, unsigned size) +{ + struct hiddev_usage_ref_multi urefm; + struct hiddev_report_info rinfo; + const gbuint8* p = buf; + unsigned i; + + for (i = 0; i < size; i++) { + urefm.values[i] = p[i]; + } + urefm.num_values = size; + memset(&urefm.uref, 0, sizeof(urefm.uref)); + urefm.uref.report_type = HID_REPORT_TYPE_OUTPUT; + if (ioctl(fd_hiddev, HIDIOCSUSAGES, &urefm)) { + fatal(MYNAME ": HIDIOCSUSAGES: %s\n", strerror(errno)); + } + memset(&rinfo, 0, sizeof(rinfo)); + rinfo.report_type = HID_REPORT_TYPE_OUTPUT; + if (ioctl(fd_hiddev, HIDIOCSREPORT, &rinfo)) { + fatal(MYNAME ": HIDIOCSREPORT: %s\n", strerror(errno)); + } + return size; +} + +static const delbin_os_ops_t linuxhid_os_ops = { + linuxhid_os_init, + linuxhid_os_deinit, + linuxhid_os_packet_read, + linuxhid_os_packet_write +}; + +static void linux_os_init(const char* fname); + +delbin_os_ops_t delbin_os_ops = { + linux_os_init, + NULL, + NULL, + NULL +}; + +static void +linux_os_init(const char* fname) +{ + // tell linuxhid_os_init not to exit + linuxhid_os_init_status = 1; + linuxhid_os_init(fname); + if (linuxhid_os_init_status == 0) { + delbin_os_ops = linuxhid_os_ops; + } else { +#if HAVE_LIBUSB + if (global_opts.debug_level >= DBGLVL_M) + warning(MYNAME ": HID init failed, falling back to libusb\n"); + delbin_os_ops = libusb_os_ops; + delbin_os_ops.init(fname); +#else + fatal(MYNAME ": no DeLorme PN found\n"); +#endif + } +} + +#endif // __linux + +//----------------------------------------------------------------------------- +// stubs +#if !(_WIN32 || __linux || HAVE_LIBUSB) +static void +stub_os_init(const char* fname) +{ + fatal(MYNAME ": OS not supported\n"); +} +static void +stub_os_deinit(void) +{ +} +static unsigned +stub_os_packet_read(void* buf) +{ + return 0; +} +static unsigned +stub_os_packet_write(const void* buf, unsigned size) +{ + return 0; +} +delbin_os_ops_t delbin_os_ops = { + stub_os_init, + stub_os_deinit, + stub_os_packet_read, + stub_os_packet_write +}; +#endif +// end OS device I/O implementations section +//============================================================================= + +static const int track_color_bgr[] = { + 0x0000ff, // red + 0x00ffff, // yellow + 0x008000, // green + 0xff0000, // blue + 0x808080, // gray + 0xffffff, // white + 0, // black + 0xffff00, // cyan + 0xff00ff, // magenta + 0x00a5ff, // orange + 0x82004b, // indigo + 0xeea5ee // violet +}; + +static int track_color(unsigned i) +{ + int bgr = -1; + if (i < sizeofarray(track_color_bgr)) { + bgr = track_color_bgr[i]; + } + return bgr; +} + +static unsigned track_color_index(int bgr) +{ + unsigned i = sizeofarray(track_color_bgr); + do { + i--; + } while (i != 0 && track_color_bgr[i] != bgr); + return i; +} + +static const char* const waypoint_symbol_name[] = { + // 0 + "Red Map Pin", + "Dark Red Map Pin", + "Yellow Map Pin", + "Dark Yellow Map Pin", + "Green Map Pin", + "Dark Green Map Pin", + "Turquoise Map Pin", + "Dark Turquoise Map Pin", + "Blue Map Pin", + "Dark Blue Map Pin", + // 10 + "Gray Map Pin", + "Dark Gray Map Pin", + "Red Flag", + "Dark Red Flag", + "Yellow Flag", + "Dark Yellow Flag", + "Green Flag", + "Dark Green Flag", + "Turquoise Flag", + "Dark Turquoise Flag", + // 20 + "Blue Flag", + "Dark Blue Flag", + "Gray Flag", + "Dark Gray Flag", + "Red Dot", + "Dark Red Dot", + "Yellow Dot", + "Dark Yellow Dot", + "Green Dot", + "Dark Green Dot", + // 30 + "Turquoise Dot", + "Dark Turquoise Dot", + "Blue Dot", + "Dark Blue Dot", + "Gray Dot", + "Dark Gray Dot", + "Small Red Dot", + "Small Dark Red Dot", + "Small Yellow Dot", + "Small Dark Yellow Dot", + // 40 + "Small Green Dot", + "Small Dark Green Dot", + "Small Turquoise Dot", + "Small Dark Turquoise Dot", + "Small Blue Dot", + "Small Dark Blue Dot", + "Small Gray Dot", + "Small Dark Gray Dot", + "Arrow Up", + "Arrow Down", + // 50 + "Arrow Left", + "Arrow Right", + "Arrow Up Left", + "Arrow Up Right", + "Arrow Down Left", + "Arrow Dow Right", + "Green Star", + "Yellow Square", + "Red X", + "Turquoise Circle", + // 60 + "Purple Triangle", + "American Flag", + "Stop", + "Parking", + "First Aid", + "Dining", + "Railroad Crossing", + "Heliport", + "Restroom", + "Information", + // 70 + "Diver Down", + "Exit", + "Health Facility", + "Police", + "Post Office", + "Mining", + "Danger", + "Money", + "Exclamation", + "Car", + // 80 + "Jeep", + "Truck", + "Tow Truck", + "Motor Home", + "School Bus", + "Four-wheeler", + "Snowmobile", + "Sailboat", + "Powerboat", + "Boat Launch", + // 90 + "Anchor", + "Buoy", + "Shipwreck", + "Glider Area", + "Private Airport", + "Public Airport", + "Military Airport", + "Military Base", + "House", + "Church", + // 100 + "Building", + "School", + "Lighthouse", + "Bridge", + "Radio Tower", + "Dam", + "Tunnel", + "Toll Booth", + "Gas Station", + "Lodging", + // 110 + "Telephone", + "Traffic Light", + "Fire Hydrant", + "Tombstone", + "Picnic Table", + "Tent", + "Shelter", + "Camper", + "Fire", + "Shower", + // 120 + "Drinking Water", + "Binoculars", + "Camera", + "Geocache", + "Geocache Found", + "Fishing Pole", + "Ice Fishing Trap Set", + "Ice Fishing Trap Up", + "Moose", + "Deer", + // 130 + "Bear", + "Bird", + "Duck", + "Fish", + "Deer Tracks", + "Animal Tracks", + "Bird Tracks", + "Birch Tree", + "Evergreen Tree", + "Deciduous Tree", + // 140 + "Flower Garden", + "Mountain", + "Cave", + "Beach", + "Hiking", + "Swimming", + "Bicycling", + "Kayaking", + "Canoeing", + "Water Skiing", + // 150 + "Cross-country Skiing", + "Downhill Skiing", + "Ice Skating", + "Dogsledding", + "Shooting", + "Golf Course", + "Ballpark", + // 157-182 added in PN-40 2.5 firmware + "Cache Found", + "Didn't Find It", + "My Cache", + // 160 + "Traditional Cache", + "Multi-Cache", + "Unknown Cache", + "Letterbox Hybrid", + "Whereigo Cache", + "Event Cache", + "Mega-Event Cache", + "Cache In Trash Out Event", + "EarthCache", + "Virtual Cache", + // 170 + "Webcam Cache", + "Waymark", + "NGS Benchmark", + "Write Note", + "Needs Maintenance", + "Final Location", + "Parking Area", + "Question to Answer", + "Reference Point", + "Stages of a Multicache", + // 180 + "Trailhead", + "Temporarily Disable Listing", + "Enable Listing" +}; + +static const char* +waypoint_symbol(unsigned i) +{ + const char* p = NULL; + if (i < sizeofarray(waypoint_symbol_name)) { + p = waypoint_symbol_name[i]; + } + return p; +} + +static unsigned +waypoint_symbol_index(const char* name) +{ + static unsigned last_result; + static char last_name[32]; + unsigned i = last_result; + + if (strncmp(name, last_name, sizeof(last_name)) != 0) { + i = sizeofarray(waypoint_symbol_name); + do { + i--; + } while (i != 0 && case_ignore_strcmp(name, waypoint_symbol_name[i]) != 0); + strncpy(last_name, name, sizeof(last_name)); + last_result = i; + } + return i; +} + +// vi: ts=4 sw=4 noexpandtab diff --git a/vecs.c b/vecs.c index 8ba8c0bbb..7d369e1bf 100644 --- a/vecs.c +++ b/vecs.c @@ -45,6 +45,7 @@ extern ff_vecs_t compegps_vecs; extern ff_vecs_t copilot_vecs; extern ff_vecs_t coto_vecs; extern ff_vecs_t cst_vecs; +extern ff_vecs_t delbin_vecs; extern ff_vecs_t dg100_vecs; extern ff_vecs_t easygps_vecs; extern ff_vecs_t garmin_vecs; @@ -926,6 +927,12 @@ vecs_t vec_list[] = { "Naviguide binary route file (.twl)", "twl" }, + { + &delbin_vecs, + "delbin", + "DeLorme PN-20/PN-30/PN-40 USB protocol", + NULL + }, #endif // MAXIMAL_ENABLED { -- 2.30.2